﻿/*****************************************************************************\
 *             University of Illinois/NCSA Open Source License
 *
 * 
 *      Copyright © 2012,2013, The Board of Trustees of the University of Illinois. All rights reserved.
 *
 *      Developed by: Rakesh Bobba, Derek Dagit, Erich Heine, Denis Kholine,
 *                    Klara Nahrstedt, David Rodgers,   William H. Sanders, Tim Yardley
 *
 *      Information Trust Institute/Coordinated Science Laboratory
 * 
 *      University of Illinois
 *      
 *      The Board of Trustees of the University of Illinois
 *      109 Coble Hall
 *      801 South Wright Street
 *      Champaign, IL 61820-6242
 *
 *      http://www.iti.illinois.edu / http://csl.illinois.edu
 * 
 *      Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
 *      associated documentation files (the “Software”), to deal with the Software without restriction, 
 *      including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *      and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
 *      subject to the following conditions:
 *
 *      Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *      following disclaimers.
 *      
 *      Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
 *      the following disclaimers in the documentation and/or other materials provided with the distribution.
 *
 *      Neither the names of Information Trust institute, Coordinated Science Laboratory, University of Illinois,
 *      The Board of Trustees of the University of Illinois nor the names of its contributors may be used to 
 *      endorse or promote products derived from this Software without specific prior written permission.
 *      
 *      THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 *      LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN
 *      NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *      WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 *      SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
 *      
 \*****************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using QuickGraph;

namespace SIEGate.Core.Configurator
{

    public class CollapseInfo
    {
        public IConfigInfo container;
        public IList<Edge<IConfigInfo>> new_edges;
        public IList<IConfigInfo> consolidated_infos;

        public CollapseInfo()
        {
            new_edges = new List<Edge<IConfigInfo>>();
            consolidated_infos = new List<IConfigInfo>();
        }
    }

    public delegate CollapseInfo CollapseDelegate(IConfigInfo adapterInfo, IBidirectionalIncidenceGraph<IConfigInfo, Edge<IConfigInfo>> graph);

    public class ConfigGraph
    {
        private static ConfigGraph _currentConfig;
        private BidirectionalGraph<IConfigInfo, Edge<IConfigInfo>> m_graph = new BidirectionalGraph<IConfigInfo, Edge<IConfigInfo>>();
        public CollapseDelegate Collapsers;

        private IDictionary<Guid, ISet<IConfigInfo>> m_destinations = new Dictionary<Guid, ISet<IConfigInfo>>();
        private IDictionary<Guid, IConfigInfo> m_sources = new Dictionary<Guid, IConfigInfo>();

        public IEnumerable<IDataProcessorInfo> Nodes
        {
            get
            {
                return m_graph.Vertices.Select(v => v as IDataProcessorInfo).Where(v => v != null);
            }
        }
        public ConfigGraph()
        {
            Collapsers += straightline;
        }

        public void Build(IList<IConfigInfo> listAdapterInfos)
        {

            foreach (IConfigInfo adapterInfo in listAdapterInfos)
            {
                m_graph.AddVertex(adapterInfo);

                foreach (IConfigInfo target in listAdapterInfos.Where(ai => adapterInfo.OutgoingStreamIDs.Intersect(ai.IncomingStreamIDs)
                    .Any()).Where(target => (object)adapterInfo != (object)target))
                {
                    m_graph.AddVerticesAndEdge(new Edge<IConfigInfo>(adapterInfo, target));
                }
            }
        }
        public void Collapse(int rounds = int.MaxValue)
        {
            bool finished = false;
            int numrounds = 0;
            while (!finished && numrounds < rounds)
            {
                numrounds++;
                bool breakout = false;
                foreach (IConfigInfo ai in m_graph.Vertices)
                {
                    foreach (CollapseInfo r in from CollapseDelegate collapseFunc in Collapsers.GetInvocationList() select collapseFunc(ai, m_graph) into r where r != null select r)
                    {
                        m_graph.RemoveVertex(ai);
                        m_graph.AddVertex(r.container);
                        foreach (IConfigInfo toRemove in r.consolidated_infos)
                        {
                            m_graph.RemoveVertex(toRemove);
                        }
                        foreach (Edge<IConfigInfo> e in r.new_edges)
                        {
                            m_graph.AddEdge(e);
                        }
                        breakout = true;
                        break;
                    }
                    if (breakout) break;
                }
                finished = true; // only get here if we go through all vertices without collapsing
            }
            // Now make sure everything has a container, even single things
            foreach (IConfigInfo ai in new List<IConfigInfo>(m_graph.Vertices))
            {
                if (ai is IDataProcessorInfo) { continue; }
                IAdapterContainerInfo newInfo = new AdapterContainer.AdapterContainerInfo(ai);
                // replace edges
                List<Edge<IConfigInfo>> newEdges = m_graph.InEdges(ai).Select(e => new Edge<IConfigInfo>(e.Source, newInfo)).ToList();
                newEdges.AddRange(m_graph.OutEdges(ai).Select(e => new Edge<IConfigInfo>(newInfo, e.Target)));
                m_graph.RemoveVertex(ai);
                m_graph.AddVertex(newInfo);
                m_graph.AddEdgeRange(newEdges);
            }
            _currentConfig = this;
        }

        // TODO: put this in its own static class
        public CollapseInfo straightline(IConfigInfo toCheck, IBidirectionalIncidenceGraph<IConfigInfo, Edge<IConfigInfo>> mygraph)
        {

            if (!(toCheck is IAdapterContainerInfo) && !(toCheck is IAdapterInfo))
            {
                return null;
            }

            CollapseInfo result = new CollapseInfo();

            if (mygraph.OutEdges(toCheck).Count() == 1)
            {
                IConfigInfo farVertex = mygraph.OutEdges(toCheck).First().Target;
                if (!(farVertex is IAdapterContainerInfo) && !(farVertex is IAdapterInfo))
                { 
                    return null;
                }

                if (mygraph.InEdges(farVertex).Count() != 1) return null;

                if (toCheck is IAdapterContainerInfo)
                {
                    result.container = toCheck;
                }
                else
                {
                    result.container = new AdapterContainer.AdapterContainerInfo(toCheck);
                    result.consolidated_infos.Add(toCheck);
                }
                try
                {
                    (result.container as IAdapterContainerInfo).Consolidate(farVertex);
                }
                catch (ArgumentException e)
                {
                    result = null;
                    return null;
                }
                result.consolidated_infos.Add(farVertex);
                
                foreach (Edge<IConfigInfo> e in mygraph.InEdges(toCheck))
                {
                    result.new_edges.Add(new Edge<IConfigInfo>(e.Source, result.container));
                }


                foreach (Edge<IConfigInfo> e in mygraph.OutEdges(farVertex))
                {
                    result.new_edges.Add(new Edge<IConfigInfo>(result.container, e.Target));
                }

                return result;
            }
            return null;
        }

        // Configuration Queries on the current graph
        /// <summary>
        /// Finds the adapter ID for the adapter that makes a signal. Can be used for sending notifications,
        /// doing rearrangements or whatever, Important to prevent code references from being passed to places
        /// that could do nefarious things
        /// </summary>
        /// <returns></returns>
        public static bool GetProviderFor(Guid whatSignal, out uint adapterID)
        {
            IDataProcessorInfo target = null;
            adapterID = 0;
            foreach (IDataProcessorInfo node in _currentConfig.m_graph.Vertices)
            {
                // just go with the first place we find the signal - incoming ids just mean find the right 
                // node in the in edges, if it's in outgoing, you know what to do.
                if (node.IncomingStreamIDs.Contains(whatSignal))
                {
                    foreach (IDataProcessorInfo source in _currentConfig.m_graph.InEdges(node).Cast<IDataProcessorInfo>()
                        .Where(source => source.OutgoingStreamIDs.Contains(whatSignal)))
                    {
                        target = source;
                        break;
                    }
                }
                else if (node.OutgoingStreamIDs.Contains(whatSignal))
                {
                    target = node;
                }
                if (target != null) break;
            }
            if (target == null) return false;
            if (target is IAdapterContainerInfo)
            {
                IAdapterContainerInfo IACItarget = target as IAdapterContainerInfo;
                adapterID = IACItarget.MemberInfos.Last().ID;
                return true;
            }
            if (target is INotificationTarget)
            {
                adapterID = target.ID;
                return true;
            }
            return false;
        }

        // HACK: No good, very bad hack. We need a better way of marking things "publishable"
        public static HashSet<Guid> AllPublishableStreams()
        {
            HashSet<Guid > res = new HashSet<Guid>();
            foreach (HashSet<Guid> oids in _currentConfig.Nodes.Select(node => node.OutgoingStreamIDs))
            {
                res.UnionWith(oids);
            }
            return res;
        }

        // debugging!
        public static string PrintGraph()
        {
            StringBuilder res = new StringBuilder();
            res.AppendLine("Graph:");
            res.AppendLine("--------------------");

            foreach (IConfigInfo info in _currentConfig.m_graph.Vertices)
            {
                res.AppendLine("NODE: " + info.Name);

                res.AppendLine("\t incoming");
                if (info.IncomingStreamIDs.Any())
                {
                    res.AppendLine("\t\t" + info.IncomingStreamIDs.Select(x => x.ToString()).Aggregate((s, x) => s + "," + x));
                }
                else
                {
                    res.AppendLine("\t\tNONE!");
                }
                res.AppendLine("\tincoming edges");
                foreach (Edge<IConfigInfo> e in _currentConfig.m_graph.InEdges(info))
                {
                    res.AppendLine(String.Format("{0} -> {1}", e.Source.Name, e.Target.Name));
                }
                res.AppendLine("\toutgoing");
                if (info.OutgoingStreamIDs.Any())
                {
                    res.AppendLine("\t\t" + info.OutgoingStreamIDs.Select(x => x.ToString()).Aggregate((s, x) => s + "," + x));
                }
                else
                {
                    res.AppendLine("\t\tNONE");
                }
                foreach (Edge<IConfigInfo> e in _currentConfig.m_graph.OutEdges(info))
                {
                    res.AppendLine(String.Format("{0} -> {1}", e.Source.Name, e.Target.Name));
                }
                res.AppendLine("---------------");
            }
            return res.ToString();
        }
    }
}

